package edu.amm.neur;

import java.util.ArrayList;
import java.util.List;

/**
 * Реализация ассоциативной сети Хопфилда.
 * 
 * @author Иванов Илья
 * @since 2013-04-27
 *
 */
public class HopfieldNetwork {
	
	/** Количество нейронов в сети */
	private int n;
	/** Матрица весов */
	private double[][] w;
	/** Энергетические минимумы сети */
	private List<BipolarVector> minimums = new ArrayList<>();
	
	/**
	 * @param n Количество нейронов в сети.
	 */
	public HopfieldNetwork(int n) {
		if (n < 1)
			throw new RuntimeException("Некорректное количество нейронов");
		
		this.n = n;
		w = new double[n][n];
	}
	
	/**
	 * Вычисляет выход сети.
	 * @param input Входной вектор.
	 * @param callback Если не <code>null</code>, то вызывается на каждой итерации
	 * с соответствующим результатом вычисления выхода.
	 * @return Полученный выходной вектор (на последней итерации).
	 */
	public BipolarVector output(BipolarVector input, IterationCallback callback) {
		if (input == null)
			return null;
		if (input.length() != n)
			throw new RuntimeException("Некорректная размерность входного вектора");
		
		BipolarVector prev = new BipolarVector(n);
		BipolarVector output = new BipolarVector(n);
		do {
			boolean stop = false;
			output.copyFrom(input);
			do {
				BipolarVector temp = prev;
				prev = output;
				output = temp;
				
				for (int j = 0; j < n; j++) {
					double s = 0;
					for (int i = 0; i < n; i++)
						s += w[i][j] * prev.get(i);
					
					output.set(j, activation(s, prev.get(j)));
				}
				
				stop = prev.equals(output);
				if (!stop && callback != null)
					callback.result(new BipolarVector(output));
			} while (!stop);
			
			boolean minimumReached = false;
			for (BipolarVector minimum : minimums)
				if (minimumReached = minimum.equals(output))
					break;
			if (minimumReached)
				break;
			else
				untrain(output);
		} while (true);
		
		return output;
	}
	
	private byte activation(double summa, byte prevJ) {
		if (summa < THETA)
			return -1;
		else if (summa == THETA)
			return prevJ;
		else
			return 1;
	}
	
	/**
	 * Обучение сети на наборе векторов.
	 * @param vectors Набор векторов - энергетических минимумов сети Хопфилда.
	 */
	public void train(List<BipolarVector> vectors) {
		int size;
		if (vectors == null || (size = vectors.size()) == 0)
			return;
		
		for (BipolarVector x : vectors)
			if (x.length() != n)
				throw new RuntimeException("Некорректный размер вектора в обучающей выборке");
		
		for (int i = 0; i < w.length; i++)
			for (int j = 0; j < w[i].length; j++)
				w[i][j] = 0;
		minimums.clear();
		
		for (int k = 0; k < size; k++) {
			BipolarVector x = vectors.get(k);
			for (int i = 0; i < w.length; i++)
				for (int j = 0; j < w[i].length; j++) {
					if (i == j)
						continue;
					
					w[i][j] += x.get(i) * x.get(j);
				}
			
			minimums.add(x);
		}
	}
	
	private void untrain(BipolarVector falseVector) {
		for (int i = 0; i < w.length; i++)
			for (int j = 0; j < w[i].length; j++) {
				if (i == j)
					continue;
				
				w[i][j] -= EPSILON * falseVector.get(i) * falseVector.get(j);
			}
	}
	
	private static final double THETA = 0;
	private static final double EPSILON = 0.1;
}